package bots.lookahead;

import bots.AbstractBot;
import model.*;
import org.apache.log4j.Logger;
import util.Pair;
import util.RandomMoveChooser;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

/**
 * @author Alex Vikharev vikharev@yandex-team.ru
 *         created 27.02.12
 */
public abstract class AbstractLookAheadBot extends AbstractBot {
    protected static final Logger log = Logger.getLogger(SimpleLookAheadBot.class);
    protected final ForkJoinPool pool;
    protected final MoveChooser chooser;

    public AbstractLookAheadBot(final String name, final GameType gameType, ForkJoinPool pool, MoveChooser chooser) {
        super(name, gameType);
        this.chooser = chooser;
        this.pool = pool;
    }

    public AbstractLookAheadBot(final String name, final GameType gameType, ForkJoinPool pool) {
        this(name, gameType, pool, new RandomMoveChooser());
    }

    protected static PositionType opposite(PositionType pos) {
        if (pos == null) {
            return PositionType.UNKNOWN;
        }
        switch (pos) {
            case WIN:
                return PositionType.LOOSE;
            case LOOSE:
                return PositionType.WIN;
            default:
                return PositionType.UNKNOWN;
        }
    }

    @Override
    public abstract Position setFigure(Field field, Figure figure);

    protected static class Result {
        final PositionType pos;
        final Position move;

        protected Result(PositionType win, Position move) {
            pos = win;
            this.move = move;
        }
    }

    protected static enum PositionType {
        WIN, LOOSE, UNKNOWN
    }

    protected class Task extends RecursiveTask<Result> {

        private Field field;
        private Figure figure;
        private int depth;

        protected Task(Field field, Figure figure, int depth) {
            this.field = field;
            this.figure = figure;
            this.depth = depth;
        }

        @Override
        protected Result compute() {
            Position winPos = getWinMove(field, figure);

            if (winPos != null) {
                log.trace("Return winning pair: " + winPos);
                return new Result(PositionType.WIN, winPos);
            } else {
                if (depth == 0) {
                    Position move = setRandomFigure(field, figure);
                    if (log.isTraceEnabled()) {
                        log.trace("Returning random move: " + move);
                    }
                    return new Result(PositionType.UNKNOWN, move);
                } else {
                    if (freeFigures(field, figure).isEmpty()) {
                        for (int x = 0; x < 4; x++) {
                            for (int y = 0; y < 4; y++) {
                                if (field.get(x, y) == null) {
                                    return new Result(PositionType.UNKNOWN, new Position(field.addFigure(x, y, figure), null));
                                }
                            }
                        }
                    }

                    List<Pair<Task, Position>> tasks = new ArrayList<>();

                    for (int x = 0; x < 4; x++) {
                        for (int y = 0; y < 4; y++) {
                            if (field.get(x, y) == null) {
                                for (Figure freeFigure : freeFigures(field, figure)) {
                                    Field newField = field.addFigure(x, y, figure);
                                    Task task = new Task(newField, freeFigure, depth - 1);
                                    tasks.add(Pair.of(task, new Position(newField, freeFigure)));
                                    task.fork();
                                }
                            }
                        }
                    }

                    PositionType bestPosType = null;
                    Position move = null;
                    List<Position> unknownMoves = new ArrayList<>();
                    for (Pair<Task, Position> task : tasks) {
                        Task first = task.getFirst();
                        Result result = first.join();
                        if (bestPosType == null || result.pos == PositionType.LOOSE || (bestPosType == PositionType.WIN && result.pos == PositionType.UNKNOWN)) {
                            bestPosType = result.pos;
                            move = task.getSecond();
                        }
                        if (result.pos == PositionType.UNKNOWN) {
                            unknownMoves.add(task.getSecond());
                        }
                    }
                    if (bestPosType == PositionType.UNKNOWN) {
                        move = chooser.choose(unknownMoves);
                    }
                    if (log.isTraceEnabled()) {
                        log.trace("Returning best move: " + move);
                    }
                    if (move == null && field.getUsedFigures().size() < 16) {
                        System.err.println(tasks);
                        System.err.println(freeFigures(field, figure));
                        System.err.println(field);
                        throw new AssertionError("Bug");
                    }
                    return new Result(opposite(bestPosType), move);

                }
            }
        }
    }
}
